#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/types.h>
#include <linux/stat.h>
#include <linux/sched.h>
#include <linux/list.h>
#include <linux/fcntl.h>
#include <linux/unistd.h>
#include <linux/ioctl.h>	
#include <linux/acpi.h>
#include <linux/platform_device.h>
#include <linux/power_supply.h>
#include <linux/proc_fs.h>
#include <linux/slab.h>
#include <linux/random.h>
#include <linux/spinlock.h>
#include <linux/time.h>
#include "batterymine.h"

MODULE_LICENSE("GPL");

#define IDLE_PROCESS_NAME	"Idle"

#define DEV_TYPE_IDLE			"Idle"
#define DEV_TYPE_WIFI			"WiFi"
#define DEV_TYPE_BLUETOOTH		"Bluetooth"
#define DEV_TYPE_AUDIO			"Audio"
#define DEV_TYPE_3G				"3G"

// Structure for holding usage info per device type
typedef struct device_type_usage
{
	enum power_consumer_type devType;	// Type of device used
	struct timespec usage;				// Amount of usage
	struct list_head list;				// Kernel linked list structure
} device_type_usage;

// Structure for holding per-process device usage
typedef struct proc_battery_usage
{
	pid_t processID; 							// Process ID
	struct device_type_usage deviceInfoList;		// List of device_type_usage structures
	struct list_head list;						// Kernel list structure
} proc_battery_usage;


// Main Data structure for holding power consumption information
typedef struct battery_consumption_info
{
	proc_battery_usage idleUsage;			// Usage unattributable to a process
	proc_battery_usage perProcessUsage;  	// Usage per process
	atomic_t ulInit;
	spinlock_t slStructLock;
} battery_consumption_info;

battery_consumption_info g_batteryUsage;

struct goldfish_battery_data 
{
	uint32_t reg_base;
	int irq;
	spinlock_t lock;

	struct power_supply battery;
	struct power_supply ac;
};

// Declaratoion of global structure pointers
struct proc_dir_entry *proc_dir;
struct proc_dir_entry *proc_processes;
struct proc_dir_entry *proc_battery;

/////////////////////////////////////////////////////////////////////
// Function prototypes
/////////////////////////////////////////////////////////////////////
static int __inspect_power_supply(struct device *dev, void *data);

int show_batt(char *buf, char **start, off_t off,
			  int len, int *eof, void *data);
			  
int show_proc(char *buf, char **start, off_t off,
			  int len, int *eof, void *data);

void printProperty(struct power_supply *psy, 
				  enum power_supply_property property,
				  char *szPropName,
				  char **pCur);
				  
void printBatteryStats(char **pCur);
void printUsageForProcess(proc_battery_usage *pProcUsage, char *szProcID, char **pCur);
char *deviceNameFromID(enum power_consumer_type devType);
				  
void initialize_battery_usage_lists(void);

void addUsageForProcess(proc_battery_usage *pProcUsage,
					  enum power_consumer_type devType, 
					  struct timespec usageTime);
					  
void clearBatteryData();
void clearProcessList(proc_battery_usage *plist);
void emptyDeviceUsageList(device_type_usage *pList);

////////////////////////////////////////////////////////////////////
// Inline/static functions
////////////////////////////////////////////////////////////////////
static inline struct timespec timespec_add(struct timespec lhs,
						struct timespec rhs)
{
	struct timespec ts_delta;
	set_normalized_timespec(&ts_delta, lhs.tv_sec + rhs.tv_sec,
				lhs.tv_nsec + rhs.tv_nsec);
	return ts_delta;
}

/* init_module is called when the module is loaded */
int __init init_module(void) 
{
	spin_lock(&(g_batteryUsage.slStructLock));
	
	proc_dir = proc_mkdir("batterymine", NULL);
	if (proc_dir == NULL)
	{
		printk("<1>ERROR:  Failed to create \"batterymine\" directory in procfs.\n");
		return -ENOMEM;
	}
	
	proc_processes = create_proc_read_entry("proc", 0444, proc_dir, show_proc, NULL);
	if (proc_processes == NULL)
	{
		printk("<1>ERROR:  Failed to create \"batterymine/proc\" directory in procfs.\n");
		return -ENOMEM;
	}
	
	proc_battery = create_proc_read_entry("batt", 0444, proc_dir, show_batt, NULL);
	if (proc_battery == NULL)
	{
		printk("<1>ERROR:  Failed to create \"batterymine/batt\" directory in procfs.\n");
		return -ENOMEM;
	}
	
	// Initialize the battery usage lists
	initialize_battery_usage_lists();	
	
	spin_unlock(&(g_batteryUsage.slStructLock));
	
	printk(KERN_EMERG "*** Batterymine - module loaded...\n");
	return 0;
}

static int __inspect_power_supply(struct device *dev, void *data)
{
	struct power_supply *psy = dev_get_drvdata(dev);
printk("Inspecting power supply device.\n");
	char **pCur = (char**)data;
	
	if (psy->type == POWER_SUPPLY_TYPE_BATTERY) 
	{
		  printk("Found the battery device!\n");
		  printk("Device name = %s\n", psy->name);
		  
		  printProperty(psy, POWER_SUPPLY_PROP_STATUS, "Battery Status", pCur);
		  printProperty(psy, POWER_SUPPLY_PROP_HEALTH, "Battery Health", pCur);
		  printProperty(psy, POWER_SUPPLY_PROP_PRESENT, "Battery Present", pCur);
		  printProperty(psy, POWER_SUPPLY_PROP_ONLINE, "Battery Online", pCur);
		  printProperty(psy, POWER_SUPPLY_PROP_TECHNOLOGY, "Battery Technology", pCur);
		  printProperty(psy, POWER_SUPPLY_PROP_VOLTAGE_MAX, "Battery Max Voltage", pCur);
		  printProperty(psy, POWER_SUPPLY_PROP_VOLTAGE_MIN, "Battery Min Voltage", pCur);
		  printProperty(psy, POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN, "Battery Max Design Voltage", pCur);
		  printProperty(psy, POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN, "Battery Min Design Voltage", pCur);
		  printProperty(psy, POWER_SUPPLY_PROP_VOLTAGE_NOW, "Battery Current Voltage", pCur);
		  printProperty(psy, POWER_SUPPLY_PROP_VOLTAGE_AVG, "Battery Average Voltage", pCur);
		  printProperty(psy, POWER_SUPPLY_PROP_CURRENT_NOW, "Battery Current Current", pCur);
		  printProperty(psy, POWER_SUPPLY_PROP_CURRENT_AVG, "Battery Average Current", pCur);
		  printProperty(psy, POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN, "Battery Designed Full Charge", pCur);
		  printProperty(psy, POWER_SUPPLY_PROP_CHARGE_EMPTY_DESIGN, "Battery Designed Empty Charge", pCur);
		  printProperty(psy, POWER_SUPPLY_PROP_CHARGE_FULL, "Battery Full Charge", pCur);
		  printProperty(psy, POWER_SUPPLY_PROP_CHARGE_EMPTY, "Battery Empty Charge", pCur);
		  printProperty(psy, POWER_SUPPLY_PROP_CHARGE_NOW, "Battery Current Charge", pCur);
		  printProperty(psy, POWER_SUPPLY_PROP_CHARGE_AVG, "Battery Average Charge", pCur);
		  printProperty(psy, POWER_SUPPLY_PROP_CHARGE_COUNTER, "Battery Charge Counter", pCur);
		  printProperty(psy, POWER_SUPPLY_PROP_ENERGY_FULL_DESIGN, "Battery Designed Full Energy", pCur);
		  printProperty(psy, POWER_SUPPLY_PROP_ENERGY_EMPTY_DESIGN, "Battery Designed Empty Energy", pCur);
		  printProperty(psy, POWER_SUPPLY_PROP_ENERGY_FULL, "Battery Full Energy", pCur);
		  printProperty(psy, POWER_SUPPLY_PROP_ENERGY_EMPTY, "Battery Designed Empty Energy", pCur);
		  printProperty(psy, POWER_SUPPLY_PROP_ENERGY_NOW, "Battery Current Energy", pCur);
		  printProperty(psy, POWER_SUPPLY_PROP_ENERGY_AVG, "Battery Average Energy", pCur);
		  printProperty(psy, POWER_SUPPLY_PROP_CAPACITY, "Battery Capacity (%)", pCur);
		  printProperty(psy, POWER_SUPPLY_PROP_TEMP, "Battery Temperature", pCur);
		  printProperty(psy, POWER_SUPPLY_PROP_TEMP_AMBIENT, "Battery Ambient Temparature", pCur);
		  printProperty(psy, POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW, "Battery Current Time to Empty", pCur);
		  printProperty(psy, POWER_SUPPLY_PROP_TIME_TO_EMPTY_AVG, "Battery Average Time to Empty", pCur);
		  printProperty(psy, POWER_SUPPLY_PROP_TIME_TO_FULL_NOW, "Battery Current Time to Full", pCur);
		  printProperty(psy, POWER_SUPPLY_PROP_TIME_TO_FULL_AVG, "Battery Average Time to Full", pCur);
		  
		  /* Properties of type `const char *' */
		  printProperty(psy, POWER_SUPPLY_PROP_MODEL_NAME, "Battery Model Name", pCur);
		  printProperty(psy, POWER_SUPPLY_PROP_MANUFACTURER, "Battery Manufacturer", pCur);
		  printProperty(psy, POWER_SUPPLY_PROP_SERIAL_NUMBER, "Battery Serial Number", pCur);
	}
	
	return 0;
}

void printProperty(struct power_supply *psy, 
				  enum power_supply_property property,
				  char *szPropName, char **pCur)
{
	// Get Value
	union power_supply_propval val;
	
	int error = psy->get_property(psy, property, &val);
		
	// Print message to syslog
	if(0 == error && val.intval)
		*pCur += sprintf(*pCur, "%s = %d\n", szPropName, val.intval);
	else if(0 == error && val.strval)
		*pCur += sprintf(*pCur, "%s = %s\n", szPropName, val.strval);
	//else
	//	printk("%s not used\n", szPropName);
}


/* cleanup_module is called when the module is unloaded */
void __exit cleanup_module(void)
{
	spin_lock(&(g_batteryUsage.slStructLock));
	
	remove_proc_entry("batt", proc_dir);
	remove_proc_entry("proc", proc_dir);
	remove_proc_entry("batterymine", NULL);
	
	spin_unlock(&(g_batteryUsage.slStructLock));
	
	printk(KERN_EMERG "*** Batterymine - module unloaded...\n");
}

/*
 * procfile_read()
 *   - returns size of buffer returned, 0 for EOF, negative on error
 */
int show_proc(char *buf, char **start, off_t off,
		int len, int *eof, void *data)
{

	printk(KERN_EMERG "*** Batterymine - read procfs\n");
	
	char *p;
	p = buf;
	
	// Code to spit out running processes
	struct list_head *pos;	//list position pointer
	struct task_struct *proc;	//current process
	
	list_for_each(pos, &init_task.tasks)
	{
		proc = list_entry(pos, struct task_struct, tasks);

		p += sprintf(p, "%d:%s\n",proc->pid, proc->comm);
	}


	return p - buf;
}

int show_batt(char *buf, char **start, off_t off,
		int len, int *eof, void *data)
{
	char *p = buf;
	
	if(NULL == power_supply_class)
		p += sprintf(p, "Cannot access power supply class pointer.\n");
	else
	{
		int error;
		error = class_for_each_device(power_supply_class, NULL, (void*)&p,
				      __inspect_power_supply);
					  
		p += sprintf(p, "\n\n");
		
		printBatteryStats((void*)&p);
	}
	
	return p - buf;
}

void initialize_battery_usage_lists()
{
	// Initialize Idle process list entry
	INIT_LIST_HEAD(&(g_batteryUsage.idleUsage.list));
	INIT_LIST_HEAD(&(g_batteryUsage.idleUsage.deviceInfoList.list));
	
	// Set idle process ID to 0
	g_batteryUsage.idleUsage.processID = 0;
	
	INIT_LIST_HEAD(&(g_batteryUsage.perProcessUsage.list));
	INIT_LIST_HEAD(&(g_batteryUsage.perProcessUsage.deviceInfoList.list));
	
	atomic_set(&(g_batteryUsage.ulInit), 1);
}

// Logs usage of a device that is unattributable to a process
void bm_logDeviceUsage(enum power_consumer_type devType, 
					   struct timespec usageTime)
{
	int nRet = 0;
	
	if(0 == atomic_read(&(g_batteryUsage.ulInit)))
		return 0;
	else
	{
		spin_lock(&(g_batteryUsage.slStructLock));
		addUsageForProcess(&(g_batteryUsage.idleUsage), devType, usageTime);
		spin_unlock(&(g_batteryUsage.slStructLock));
	}
}

// Logs usage of a device by a process
void bm_logProcDeviceUsage(enum power_consumer_type devType, 
							   pid_t processID, 
							   struct timespec usageTime)
{
	proc_battery_usage *tmp;
	struct list_head *pos;
	int nFoundMatch = 0;
	
	if(0 == atomic_read(&(g_batteryUsage.ulInit)))
		return;
	
	spin_lock(&(g_batteryUsage.slStructLock));
	
	// BUG:  This is a hack to demonstrate how the data structure works
	unsigned long ulProcID;
	get_random_bytes((void*)&ulProcID, sizeof(ulProcID));
	ulProcID %= 15;
	ulProcID += 120;

	processID = ulProcID;
	
	// Loop through process's list of device usage.
	list_for_each(pos, &(g_batteryUsage.perProcessUsage.list))
	{
		tmp = list_entry(pos, proc_battery_usage, list);
		
		// If the process has an entry, then update it
		// then update its usage statistic
		if(NULL != tmp && tmp->processID == processID)
		{
			addUsageForProcess(tmp, devType, usageTime);
			nFoundMatch = 1;
		}
	}
	
	// If the process has no entry,
	// then we need to add a new entry to its list
	if(0 == nFoundMatch)
	{
		// allocate a new list entry
		tmp = (proc_battery_usage *)kmalloc(sizeof(proc_battery_usage), GFP_KERNEL);
		
		if(NULL != tmp)
		{
			// Set the new entry's properties
			tmp->processID = processID;
			
			// Initialize the new Process' device info list
			INIT_LIST_HEAD(&(tmp->deviceInfoList.list));

			addUsageForProcess(tmp, devType, usageTime);
			
			// Add the new entry to the list_entry
			list_add(&(tmp->list), &(g_batteryUsage.perProcessUsage.list));
		}
	}

	spin_unlock(&(g_batteryUsage.slStructLock));
	
	return;
}

void addUsageForProcess(proc_battery_usage *pProcUsage,
					  enum power_consumer_type devType, 
					  struct timespec usageTime)
{
	device_type_usage *tmp;
	struct list_head *pos;
	int nFoundMatch = 0;
	
	// Loop through process's list of device usage.
	list_for_each(pos, &(pProcUsage->deviceInfoList.list))
	{
		tmp = list_entry(pos, device_type_usage, list);
		
		// If the process has used this type of device before,
		// then update its usage statistic
		if(NULL != tmp && tmp->devType == devType)
		{
			tmp->usage = timespec_add(tmp->usage, usageTime);
			nFoundMatch = 1;
		}
	}
	
	// If the process has not used this type of device before,
	// then we need to add a new entry to its list
	if(0 == nFoundMatch)
	{
		// allocate a new list entry
		tmp = (device_type_usage *)kmalloc(sizeof(device_type_usage), GFP_KERNEL);
		
		if(NULL != tmp)
		{
			// Set the new entry's properties
			tmp->devType = devType;
			tmp->usage = usageTime;
			
			// Add the new entry to the list_entry
			list_add(&(tmp->list), &(pProcUsage->deviceInfoList.list));
		}
	}
	
	return;
}

void clearBatteryData()
{
	//clearProcessList(&(g_batteryUsage.idleUsage));
	emptyDeviceUsageList(&(g_batteryUsage.idleUsage.deviceInfoList));
	clearProcessList(&(g_batteryUsage.perProcessUsage));
}

void clearProcessList(proc_battery_usage *pList)
{
	proc_battery_usage *tmp;
	struct list_head *pos;
	struct list_head *n;
	
	list_for_each_safe(pos, n, &(pList->list))
	{
		tmp = list_entry(pos, proc_battery_usage, list);
		
		// If the process has used this type of device before,
		// then update its usage statistic
		if(NULL != tmp)
		{			
			list_del(pos);
			emptyDeviceUsageList(&(tmp->deviceInfoList));
			kfree(tmp);
		}
	}
}

void emptyDeviceUsageList(device_type_usage *pList)
{
	device_type_usage *tmp;
	struct list_head *pos;
	struct list_head *n;
	
	list_for_each_safe(pos, n, &(pList->list))
	{
		tmp = list_entry(pos, device_type_usage, list);
		
		// If the process has used this type of device before,
		// then update its usage statistic
		if(NULL != tmp)
		{
			list_del(pos);
			kfree(tmp);
		}
	}
}

void printBatteryStats(char **pCur)
{
	if(0 == atomic_read(&(g_batteryUsage.ulInit)))
		return;
	else
	{
		spin_lock(&(g_batteryUsage.slStructLock));
		
		// Print idle stats
		printUsageForProcess(&(g_batteryUsage.idleUsage), IDLE_PROCESS_NAME, pCur);
		
		proc_battery_usage *tmp;
		struct list_head *pos;
		
		// Loop through process's list of device usage.
		list_for_each(pos, &(g_batteryUsage.perProcessUsage.list))
		{
			tmp = list_entry(pos, proc_battery_usage, list);
			
			if(NULL != tmp)
			{
				char szPID[50];
				snprintf(szPID, 49, "%d", tmp->processID);
				printUsageForProcess(tmp, szPID, pCur);
			}
		}
		
		clearBatteryData();
		
		spin_unlock(&(g_batteryUsage.slStructLock));
	}
}

void printUsageForProcess(proc_battery_usage *pProcUsage, char *szProcID, char **pCur)
{
	device_type_usage *tmp;
	struct list_head *pos;
	
	// Print process name or ID
	*pCur += sprintf(*pCur, "%s\t", szProcID);
	
	// Loop through process's list of device usage.
	list_for_each(pos, &(pProcUsage->deviceInfoList.list))
	{
		tmp = list_entry(pos, device_type_usage, list);
		
		// If the process has used this type of device before,
		// then update its usage statistic
		if(NULL != tmp)
		{
			char *szDevType = deviceNameFromID(tmp->devType);
			if(NULL != szDevType)
				*pCur += sprintf(*pCur, "%s=%ld.%ld\t", szDevType, tmp->usage.tv_sec, tmp->usage.tv_nsec);
		}
	}
	
	*pCur += sprintf(*pCur, "\n");
}

char *deviceNameFromID(enum power_consumer_type devType)
{
	char *szDevType = NULL;
	if(idle == devType)
		szDevType = DEV_TYPE_IDLE;
	else if(wifi == devType)
		szDevType = DEV_TYPE_WIFI;
	else if(bluetooth == devType)
		szDevType = DEV_TYPE_BLUETOOTH;
	else if(audio == devType)
		szDevType = DEV_TYPE_AUDIO;
	else if(threeG == devType)
		szDevType = DEV_TYPE_3G;
  
	return szDevType;
}

EXPORT_SYMBOL(bm_logDeviceUsage);
EXPORT_SYMBOL(bm_logProcDeviceUsage);
module_init(init_module);
module_exit(cleanup_module);